#include "files_and_reply.h"
#include <errno.h>
#include <stdio.h>
#include <Windows.h>
#include <MinWinBase.h>
#include <strsafe.h>
#include <stdlib.h>
#include "config.h"

/* NUMFILES is the maximum number of files in folder
* SEARCHPATH that is scanned. */
#define NUMFILES 1024

/* MAXLENINTTOA is the maximum count of chars when
* converting an int to char.*/
#define MAXLENINTTOA 33

/* LENCONTENT is the maximum length of the string
* when concatenating parts of strings, e.g.
* "Content-Length: 4567", including the trailing '\0'. */
#define LENCONTENT ( 15 + MAXLENINTTOA + 1)

#pragma comment(lib, "User32.lib")

/* responseMutex is used to mutually exclude threads
* when building a response header and file content.*/
static HANDLE responseMutex = NULL;

/* allFilesInDir contains all files in folder SEARCHPATH */
static char allFilesInDir[NUMFILES][MAX_PATH];

/* currentFileNr keep track of how many 
* files are in folder SEARCHPATH. */
static currentFileNr = 0;

/* fName is a temporary array that keep
* a file's name. */
static char fName[MAX_PATH];

/* searchedFileName is used to keep the name 
* of a searched file. */
static char searchedFileName[MAX_PATH];

/* mediaFile is the file that is opened
* fSize is the size of mediaFile. */
static FILE *mediaFile = NULL;
static int fSize = 0;

/* totHeaderAndFileLen is the total number of bytes 
* for the cocatenated header and file together. */
static int totHeaderAndFileLen = 0;

/* mediaBuffer is keeping the content of mediaFile. 
* mediaFilename is the name of the file mediaFile. */
static char *mediaBuffer = NULL;
static char *mediaFilename = NULL;

/* HTTPFormat is the format on which the header is
* printed. This format is the same as printf("%s") takes. */
static char *HTTPFormat = "%s%s%s%s%s%s%s%s";


/*Static private functions*/

static errorT extractFileName(char **HTTPRAR);
static errorT isFileInFolder(char *filename);
static errorT openFile(char *filename);
static errorT readFileIntoBuffer();
static errorT closeFile();
static errorT generateHTTPResponse(errorT error, char **HTTPRAR, char **fullHTTPResponse);
static errorT setContentType(char *filename, char **contentType);
static errorT generateContentLength(int fileSize, char **contentLength);
static errorT writeLog(char *domBuf, char *portBuf, char **HTTPReq);
static errorT generateHTMLError(errorT error, char **HTTPRAR, char **fullHTTPResponse);
static errorT  ifModifiedSince(char **HTTPRAR);

/* Strings that are used when building the HTTP
* response header. */
static char *HTTPContentLength[LENCONTENT];
static char *HTTPHeaderEnding = "\r\n";
static char *HTTPConnection = "Connection: close\r\n";
static char *HTTP200 = "HTTP/1.1 200 OK\r\n";
static char *HTTP400 = "HTTP/1.1 400 Bad Request\r\n";
static char *HTTP404 = "HTTP/1.1 404 Not Found\r\n";
static char *HTTP304 = "HTTP/1.1 304 Not Modified\r\n";

static char *cacheControl = "Cache-Control: public, max-age=10\r\n";

static char *HTTP404Body =
"<!DOCTYPE html>"
"<html><head>"
"<title>404 Not Found</title>"
"</head>"
"<body>"
"<h1>404 Not Found</h1>"
"<p>The requested URL was not found.</p>"
"</body></html>";

static char *HTTP400Body =
"<!DOCTYPE html>"
"<html><head>"
"<title>400 Bad Request</title>"
"</head>"
"<body>"
"<h1>400 Bad Request</h1>"
"<p>The requested URL was malformed.</p>"
"</body></html>";

/*No body should be sent to client when Conditional GET is issued.*/
static char *HTTP304Body =
"";

static char *CTtext = "Content-Type: text/html\r\n";
static char *CTpng = "Content-Type: image/png\r\n";
static char *CTjpeg = "Content-Type: image/jpeg\r\n";
static char *CTgif = "Content-Type: image/gif\r\n";
static char *CTzip = "Content-Type: application/zip\r\n";

/* logging to struct log.*/
static logT log;

/* The call comes from domain, e.g. www.bt.se*/
static char *domain = NULL;

/* The call comes from origin port, e.g. 8080 */
static char *port = NULL;

/*The response from the server, e.g 200 OK.*/
static char *HTTPResponse = NULL;

errorT initFilesAndReply(){

	WIN32_FIND_DATAA fileData;
	HANDLE fileHandle;
	int i;

	log = (logT)malloc(sizeof(*log));

	responseMutex = CreateMutex(NULL, 0, NULL);

	/*Build the full searchpath */
	StringCchCopyA(fName, MAX_PATH, SEARCHPATH);
	StringCchCatA(fName, MAX_PATH, "*");

	fileHandle = FindFirstFileA( fName, &fileData);

	if (fileHandle == INVALID_HANDLE_VALUE)
		return fileError;

	for (i = 0; i < NUMFILES; i++){ /* Scan the folder for all files and store them in array.*/

		if (fileData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY){
			StringCchCopyA((STRSAFE_LPSTR)(allFilesInDir + currentFileNr), MAX_PATH, fileData.cFileName);
			currentFileNr++;
		}

		printf("%s\n", fileData.cFileName);

		if (FindNextFileA(fileHandle, &fileData) == 0)
			break;

	}

	return allOK;
}

/*
* Function: getFile
* Usage: error = getFile(file, fSize, HTTPRAR);
* ------------------------------------------
* This function returns
*/
errorT getFile(char **fileBuffer, int *size, char **HTTPReq, char *domBuf, char *portBuf){

	errorT error = fileNotFound;

	char *fileName = NULL;
	*fileBuffer = NULL;
	*size = 0;

	WaitForSingleObject(responseMutex, INFINITE);

	if ((error = ifModifiedSince(HTTPReq)) == allOK){
		if ((error = extractFileName(HTTPReq)) == allOK){
			if ((error = isFileInFolder(mediaFilename)) == allOK){

				printf("\n\nFile %s is in folder.\n", mediaFilename);

				if ((error = openFile(mediaFilename)) == allOK)
					if ((error = fileSize(&fSize, mediaFile)) == allOK)
						if ((error = readFileIntoBuffer()) == allOK)
							error = closeFile();
			}

		}
	}

	error = generateHTTPResponse(error, HTTPReq, fileBuffer);
	*size = totHeaderAndFileLen;

	writeLog(domBuf, portBuf, HTTPReq);

	ReleaseMutex(responseMutex);

	return error;
}


static errorT generateHTTPResponse(errorT error ,char **HTTPRAR, char **fullHTTPResponse){

	char *htmlTime = NULL;
	char *contentType = NULL;
	char *contentLength = NULL;
	int headerLength = 0;
	char *fullHeaderAndMediaBuf = NULL;
	int i;

	if (error != allOK)
		error = generateHTMLError(error, HTTPRAR, fullHTTPResponse);
	else{
		HTTPResponse = HTTP200; //Used for logging.

		getHTMLTime(&htmlTime);
		setContentType(mediaFilename, &contentType);
		generateContentLength(fSize, &contentLength);

		/*Find out the length of the header before allocating memory for it. */
		headerLength = _scprintf(HTTPFormat, HTTP200, cacheControl, htmlTime, "\r\n", HTTPConnection, contentType, contentLength, HTTPHeaderEnding);

		totHeaderAndFileLen = headerLength + fSize;

		fullHeaderAndMediaBuf = (char *)malloc(totHeaderAndFileLen);
		memset(fullHeaderAndMediaBuf, '\0', totHeaderAndFileLen);

		sprintf(fullHeaderAndMediaBuf, HTTPFormat, HTTP200, cacheControl, htmlTime, "\r\n", HTTPConnection, contentType, contentLength, HTTPHeaderEnding);

		/* Copy the chars from the file and put them after the header. */
		for (i = 0; i < fSize; i++)
			fullHeaderAndMediaBuf[i + headerLength] = mediaBuffer[i];

		*fullHTTPResponse = fullHeaderAndMediaBuf;
	}

	return error;
}

static errorT generateHTMLError(errorT error, char **HTTPRAR, char **fullHTTPResponse){

	char *htmlTime = NULL;
	char *contentType = NULL;
	char *contentLength = NULL;
	int headerLength = 0;
	char *fullHeaderAndMediaBuf = NULL;
	int bodyLength = 0;
	char *bodySource = NULL;

	getHTMLTime(&htmlTime);
	setContentType("error.html", &contentType);

	if (error == fileNotFound){
		HTTPResponse = HTTP404; //Used for logging.

		bodyLength = strlen(HTTP404Body);
		bodySource = HTTP404Body;
	}

	if (error == commanNotFound){
		HTTPResponse = HTTP400; //Used for logging.

		bodyLength = strlen(HTTP400Body);
		bodySource = HTTP400Body;
	}

	if (error == fileNotModified){
		HTTPResponse = HTTP304; //Used for logging.

		bodyLength = strlen(HTTP304Body);
		bodySource = HTTP304Body;
	}
	
	generateContentLength(bodyLength, &contentLength);

	/*Find out the length of the header before allocating memory for it. */
	headerLength = _scprintf(HTTPFormat, HTTPResponse, htmlTime, "\r\n", HTTPConnection, contentType, contentLength, HTTPHeaderEnding);

	totHeaderAndFileLen = headerLength + bodyLength + 1;

	fullHeaderAndMediaBuf = (char *)malloc(totHeaderAndFileLen);
	memset(fullHeaderAndMediaBuf, '\0', totHeaderAndFileLen);

	sprintf(fullHeaderAndMediaBuf, HTTPFormat, HTTPResponse, htmlTime, "\r\n", HTTPConnection, contentType, contentLength, HTTPHeaderEnding);

	/* Copy the chars from the body and put them after the header. */
	strcat(fullHeaderAndMediaBuf, bodySource);

	*fullHTTPResponse = fullHeaderAndMediaBuf;

	return allOK;
}

errorT fileSize(int *size, FILE *file){

	*size = 0;

	fseek(file, 0, SEEK_END);

	if ((*size = ftell(file)) == -1L){
		if (errno == EBADF || errno == EINVAL)
			return streamError;
		else
			return unknownError;
	}

	fseek(file, 0, SEEK_SET);

	return allOK;
}

errorT endFilesAndReply(){

	free(log);

	return allOK;
}

static errorT extractFileName(char **HTTPRAR){

	char *command;
	char *fileString;
	char *copyOfHTTPRAR = _strdup(*HTTPRAR);
	errorT error = commanNotFound;


	command = strtok(copyOfHTTPRAR, " /\r\n");
	fileString = strtok(NULL, " /\r\n");

	mediaFilename = NULL;

	if (command != NULL && fileString != NULL){
		if (strcmp(command, "GET") == 0 && strcmp(fileString, "HTTP") == 0){
			mediaFilename = NAMEROOTPAGE;
			error = allOK;
		}
		else if (strcmp(command, "GET") == 0 && strcmp(fileString, "HTTP") != 0){
			strcpy(searchedFileName, fileString);
			mediaFilename = searchedFileName;
			error = allOK;
		}
		else
			mediaFilename = NULL;

	}

	if (copyOfHTTPRAR != NULL)
		free(copyOfHTTPRAR);

	return error;

}

static errorT isFileInFolder(char *filename){
	
	int i;
	errorT error = fileNotFound;

	for (i = 0; i < currentFileNr; i++){
	
		if (strcmp(filename, (const char*)(allFilesInDir + i)) == 0){
			error = allOK;
			break;
		}
	}

	return error;
}

static errorT openFile(char *filename){

	errorT error = allOK;

	char currentFile[MAX_PATH];
	currentFile[0] = '\0';
	strcat_s(currentFile, MAX_PATH, SEARCHPATH);
	strcat_s(currentFile, MAX_PATH, filename);

	fopen_s(&mediaFile, currentFile, "rb");

	if (mediaFile == NULL)
		error = fileOpenError;

	return error;
}

static errorT closeFile(){

	errorT error = fileError;

	if (mediaFile != NULL)
		if (fclose(mediaFile) == 0){
			mediaFile = NULL;
			error = allOK;
		}

	return error;
}

static errorT readFileIntoBuffer(){

	char *p;
	int readChars = 0; 

	if (mediaBuffer != NULL)
		free(mediaBuffer);

	mediaBuffer = (char *)malloc(fSize);
	memset(mediaBuffer, '\0', fSize);

	for (p = mediaBuffer; p < (mediaBuffer + fSize); p += readChars)
		readChars = fread((void *)p, sizeof(char), fSize, mediaFile);


	if (ferror(mediaFile))
		return fileError;

	return allOK;

}

static errorT setContentType(char *filename, char **contentType){

	char *copyOfFilename = NULL;
	char *ending = NULL;
	errorT error = allOK;

	*contentType = "";
	error = unknownContent;

	if (filename != NULL){

		copyOfFilename  = _strdup(filename);

		copyOfFilename = _strlwr(copyOfFilename);

		ending = strtok(copyOfFilename, ".");

		ending = strtok(NULL, ".");

		if (ending != NULL){
			if (strcmp(ending, "html") == 0 || strcmp(ending, "htm") == 0)
				*contentType = CTtext;

			else if (strcmp(ending, "png") == 0)
				*contentType = CTpng;

			else if (strcmp(ending, "jpg") == 0 || strcmp(ending, "jpeg") == 0)
				*contentType = CTjpeg;

			else if (strcmp(ending, "gif") == 0)
				*contentType = CTgif;

			else if (strcmp(ending, "zip") == 0)
				*contentType = CTzip;
			else
				error = unknownContent;
		}
	}

	if (copyOfFilename != NULL)
		free(copyOfFilename);

	return error;
}

static errorT generateContentLength(int fileSize, char **contentLength){

	char fileSizesAsString[MAXLENINTTOA];
	memset(HTTPContentLength, '\0', LENCONTENT);

	strcpy((char *)HTTPContentLength, "Content-Length: ");
	strcat((char *)HTTPContentLength, _itoa(fileSize, fileSizesAsString, 10));
	strcat((char *)HTTPContentLength, "\r\n");

	*contentLength = (char *)HTTPContentLength;

	return allOK;
}

static errorT writeLog(char *domBuf, char *portBuf, char **HTTPReq){

	char *HTTPReqCopy = _strdup(*HTTPReq);
	char *HTTPCommand = strtok(HTTPReqCopy, "\r\n");

	char *HTTPResponseCopy = _strdup(HTTPResponse);
	char *HTTPResp = strtok(HTTPResponseCopy, "\r\n");

	log->domainName = domBuf;
	log->portName = portBuf;
	log->HTTPCommand = HTTPCommand;
	log->serverHTTPAnswer = HTTPResp;
	log->serverSentBytes = totHeaderAndFileLen;

	saveLogToFile(log);

	free(HTTPReqCopy);
	free(HTTPResponseCopy);

	return allOK;
}

static errorT ifModifiedSince(char **HTTPRAR){

	errorT error = allOK;

	char *HTTPRARCopy = _strdup(*HTTPRAR);

	HTTPRARCopy = _strlwr(HTTPRARCopy);

	if (strstr(HTTPRARCopy, "if-modified-since") != NULL)
		error = fileNotModified;

	free(HTTPRARCopy);

	return error;
}